Merge pull request #909 from cantino/fix_regex_replace

Fix regex_replace

Akinori MUSHA 9 years ago
parent
commit
9c9cd011f4

+ 64 - 7
app/concerns/liquid_interpolatable.rb

@@ -201,15 +201,15 @@ module LiquidInterpolatable
201 201
         'concat(' << subs.join(', ') << ')'
202 202
       end
203 203
     end
204
-    
205
-    def regex_replace(input, regex, replacement = ''.freeze)
206
-      input.to_s.gsub(Regexp.new(regex), replacement.to_s)
204
+
205
+    def regex_replace(input, regex, replacement = nil)
206
+      input.to_s.gsub(Regexp.new(regex), unescape_replacement(replacement.to_s))
207 207
     end
208
-    
209
-    def regex_replace_first(input, regex, replacement = ''.freeze)
210
-      input.to_s.sub(Regexp.new(regex), replacement.to_s)
208
+
209
+    def regex_replace_first(input, regex, replacement = nil)
210
+      input.to_s.sub(Regexp.new(regex), unescape_replacement(replacement.to_s))
211 211
     end
212
-    
212
+
213 213
     private
214 214
 
215 215
     def logger
@@ -221,6 +221,63 @@ module LiquidInterpolatable
221 221
           Logger.new(STDERR)
222 222
         end
223 223
     end
224
+
225
+    BACKSLASH = "\\".freeze
226
+
227
+    UNESCAPE = {
228
+      "a" => "\a",
229
+      "b" => "\b",
230
+      "e" => "\e",
231
+      "f" => "\f",
232
+      "n" => "\n",
233
+      "r" => "\r",
234
+      "s" => " ",
235
+      "t" => "\t",
236
+      "v" => "\v",
237
+    }
238
+
239
+    # Unescape a replacement text for use in the second argument of
240
+    # gsub/sub.  The following escape sequences are recognized:
241
+    #
242
+    # - "\\" (backslash itself)
243
+    # - "\a" (alert)
244
+    # - "\b" (backspace)
245
+    # - "\e" (escape)
246
+    # - "\f" (form feed)
247
+    # - "\n" (new line)
248
+    # - "\r" (carriage return)
249
+    # - "\s" (space)
250
+    # - "\t" (horizontal tab)
251
+    # - "\u{XXXX}" (unicode codepoint)
252
+    # - "\v" (vertical tab)
253
+    # - "\xXX" (hexadecimal character)
254
+    # - "\1".."\9" (numbered capture groups)
255
+    # - "\+" (last capture group)
256
+    # - "\k<name>" (named capture group)
257
+    # - "\&" or "\0" (complete matched text)
258
+    # - "\`" (string before match)
259
+    # - "\'" (string after match)
260
+    #
261
+    # Octal escape sequences are deliberately unsupported to avoid
262
+    # conflict with numbered capture groups.  Rather obscure Emacs
263
+    # style character codes ("\C-x", "\M-\C-x" etc.) are also omitted
264
+    # from this implementation.
265
+    def unescape_replacement(s)
266
+      s.gsub(/\\(?:([\d+&`'\\]|k<\w+>)|u\{([[:xdigit:]]+)\}|x([[:xdigit:]]{2})|(.))/) {
267
+        if c = $1
268
+          BACKSLASH + c
269
+        elsif c = ($2 && [$2.to_i(16)].pack('U')) ||
270
+                  ($3 && [$3.to_i(16)].pack('C'))
271
+          if c == BACKSLASH
272
+            BACKSLASH + c
273
+          else
274
+            c
275
+          end
276
+        else
277
+          UNESCAPE[$4] || $4
278
+        end
279
+      }
280
+    end
224 281
   end
225 282
   Liquid::Template.register_filter(LiquidInterpolatable::Filters)
226 283
 

+ 8 - 0
config/initializers/liquid.rb

@@ -0,0 +1,8 @@
1
+module Liquid
2
+  # https://github.com/Shopify/liquid/pull/623
3
+  remove_const :PartialTemplateParser
4
+  remove_const :TemplateParser
5
+
6
+  PartialTemplateParser       = /#{TagStart}.*?#{TagEnd}|#{VariableStart}(?:(?:[^'"{}]+|#{QuotedString})*?|.*?)#{VariableIncompleteEnd}/m
7
+  TemplateParser              = /(#{PartialTemplateParser}|#{AnyStartingTag})/m
8
+end

+ 30 - 16
spec/concerns/liquid_interpolatable_spec.rb

@@ -177,23 +177,37 @@ describe LiquidInterpolatable::Filters do
177 177
         expect(@agent.interpolated['long_url']).to eq('http://2many.x/6')
178 178
       end
179 179
     end
180
-    
181
-    describe 'regex replace' do
182
-      let(:agent) { Agents::InterpolatableAgent.new(name: "test") }
183
-
184
-      it 'should replace the first occurrence of a string using regex' do
185
-        agent.interpolation_context['something'] = 'foobar foobar'
186
-        agent.options['cleaned'] = '{{ something | regex_replace_first: "\S+bar", "foobaz"  }}'
187
-        expect(agent.interpolated['cleaned']).to eq('foobaz foobar')
188
-      end
180
+  end
189 181
 
190
-      it 'should replace the all occurrences of a string using regex' do
191
-        agent.interpolation_context['something'] = 'foobar foobar'
192
-        agent.options['cleaned'] = '{{ something | regex_replace: "\S+bar", "foobaz"  }}'
193
-        expect(agent.interpolated['cleaned']).to eq('foobaz foobaz') 
194
-      end
195
-    
182
+  describe 'regex_replace_first' do
183
+    let(:agent) { Agents::InterpolatableAgent.new(name: "test") }
184
+
185
+    it 'should replace the first occurrence of a string using regex' do
186
+      agent.interpolation_context['something'] = 'foobar foobar'
187
+      agent.options['cleaned'] = '{{ something | regex_replace_first: "\S+bar", "foobaz"  }}'
188
+      expect(agent.interpolated['cleaned']).to eq('foobaz foobar')
189
+    end
190
+
191
+    it 'should support escaped characters' do
192
+      agent.interpolation_context['something'] = "foo\\1\n\nfoo\\bar\n\nfoo\\baz"
193
+      agent.options['test'] = "{{ something | regex_replace_first: '\\\\(\\w{2,})', '\\1\\\\' | regex_replace_first: '\\n+', '\\n'  }}"
194
+      expect(agent.interpolated['test']).to eq("foo\\1\nfoobar\\\n\nfoo\\baz")
195
+    end
196
+  end
197
+
198
+  describe 'regex_replace' do
199
+    let(:agent) { Agents::InterpolatableAgent.new(name: "test") }
200
+
201
+    it 'should replace the all occurrences of a string using regex' do
202
+      agent.interpolation_context['something'] = 'foobar foobar'
203
+      agent.options['cleaned'] = '{{ something | regex_replace: "\S+bar", "foobaz"  }}'
204
+      expect(agent.interpolated['cleaned']).to eq('foobaz foobaz')
205
+    end
206
+
207
+    it 'should support escaped characters' do
208
+      agent.interpolation_context['something'] = "foo\\1\n\nfoo\\bar\n\nfoo\\baz"
209
+      agent.options['test'] = "{{ something | regex_replace: '\\\\(\\w{2,})', '\\1\\\\' | regex_replace: '\\n+', '\\n'  }}"
210
+      expect(agent.interpolated['test']).to eq("foo\\1\nfoobar\\\nfoobaz\\")
196 211
     end
197
-    
198 212
   end
199 213
 end